//头源分离，hpp将头和源放在一起，一个文件就能够完成整个程序
#pragma once
#include <iostream>
#include <vector>
#include <cstdlib>
#include <unistd.h>
#include <string>
#include "Task.hpp"
#include <sys/wait.h>
#include <sys/types.h>

//先描述
class Channel
{
    public:
    Channel(int fd, pid_t id)
        : _wfd(fd)
        , _subid(id)
    {
        _name = "channel-" + std::to_string(_wfd) + "-" + std::to_string(_subid);
    }

    ~Channel()
    {

    }

    void Send(int code)
    {
        int n = write(_wfd, &code, sizeof(code));
        (void)n;//绕过编译器对未使用变量的语法检查
    }

    void Close()
    {
        close(_wfd);
    }

    void Wait()
    {
        pid_t rid = waitpid(_subid, nullptr, 0);
    }

    int Fd() { return _wfd;}
    pid_t SubId() { return _subid;}
    std::string Name() { return _name;}

private:
    int _wfd;//信道
    pid_t _subid;//子进程的进程编号
    std::string _name;
};

//再组织
class ChannelManager
{
    public:
    ChannelManager()
        : _next(0)
    {

    }

    void Insert(int wfd, pid_t subid)
    {
        //_channels.emplace_back(wfd, subid);
        Channel c(wfd, subid);
        _channels.push_back(c);
    }

    void PrintChannel()
    {
        for(auto& e : _channels)
        {
            std::cout << e.Name() << std::endl;
        }
    }

    Channel& Select()
    {
        auto& c = _channels[_next];
        _next++;
        _next %= _channels.size();
        return c;
    }

    void StopProcess()
    {
        for(auto& e :_channels)
        {
            std::cout << "关闭" << e.Name() << std::endl;
            e.Close();
        }
    }

    void WaitProcess()
    {
        for(auto& e :_channels)
        {
            std::cout << "回收" << e.Name() << std::endl;
            e.Wait();
        }
    }

    void CloseAll()
    {
        //这里不需要倒着关，因为从第一个子进程创建开始就在调用CloseAll，而每增加一个引用计数都会
        //被以这里的方式减去
        for(auto& channel : _channels)
        {
            std::cout << "关闭其他写端" << channel.Fd() << std::endl;
            channel.Close();
        }
    }

    void CloseAndWait()
    {
        //解决方案一：最后一个进程的写端只有自己，因此最后一个进程的一定会被关闭
        //之后往前一个的也能够被正确释放，所以倒着是可以关闭的
        // for(int i = _channels.size() - 1; i >= 0; i--)
        // {
        //     _channels[i].Close();
        //     std::cout << "关闭：" << _channels[i].Name() << std::endl;
        //     _channels[i].Wait();
        //     std::cout << "等待：" << _channels[i].Name() << std::endl;
        // }
        //解决方案二：真的让父进程一人指向所有管道的写端

    }

    ~ChannelManager()
    {

    }
    private:
    std::vector<Channel> _channels;
    int _next;
};

const int gdefaultnum = 5;

class ProcessPool{
public:
    ProcessPool(int num)
        : _process_num(num)
    {
        _tm.Register(PrintLog);
        _tm.Register(DownLoad);
        _tm.Register(UpLoad);
    }

    void Work(int rfd)
    {
        int code = 0;
        while(1)
        {
            //这里就是最初一版写法的问题所在，当管道的写端的引用计数为零时，read才会被关闭，但是
            //当父进程创建子进程时，子进程会拷贝文件描述符表，导致创建多少个子进程该管道写端的
            //引用计数就会是几，因此最初的写法中read一直处于阻塞状态，因为会有其他兄弟进程的
            //文件描述符表中有已经关闭的写端
            int n = read(rfd, &code, sizeof(code));
            if(n > 0)
            {
                if(n != sizeof(code))
                {
                    continue;
                }
                std::cout << "子进程" << getpid() << "收到一个任务码:" << code << std::endl;
                _tm.Execute(code);
            }
            else if(n == 0)
            {
                std::cout << "子进程退出"  << std::endl;
                break;
            }
            else 
            {
                std::cout << "读取失败" << std::endl;
                break;
            }
        }
    }

    bool Create()
    {
        for(int i = 0; i < _process_num; i++)
        {
            //创建信道
            int pipefd[2] = {0};
            int n = pipe(pipefd);
            if(n < 0)
            {
                return false;
            }

            //创建子进程_同时继承而来的兄弟进程的写端全部关闭
            pid_t subid = fork();
            if(subid < 0)
            {
                return false;
            }
            else if(subid == 0)
            {
                std::cout << "子进程已创建" << std::endl;
                //因为写时拷贝，所以子进程在这里拿到的是自己被创建以前的channel manager
                _cm.CloseAll();
                close(pipefd[1]);
                //子进程
                Work(pipefd[0]);
                close(pipefd[0]);
                exit(0);
            }
            else
            {
                //父进程
                close(pipefd[0]);
                _cm.Insert(pipefd[1], subid);
            }
        }
        return true;
    }

    void Debug()
    {
        _cm.PrintChannel();
    }

    void Run()
    {
        //选择一个任务
        int taskcode = _tm.Code();
        //负载均衡与负载不均衡
        //1、轮询 2、随机 3、channel添加负载指标
        //这里简单实现为轮询

        //选择一个信道，负载均衡的选择一个子进程，完成任务
        auto& c = _cm.Select();
        std::cout << "选择一个子进程:" << c.Name() << std::endl;

        //发送任务
        c.Send(taskcode);
        std::cout << "发送了一个任务码" << taskcode << std::endl;
    }

    void Stop()
    {
        //关闭父进程所有的wfd即可
        _cm.StopProcess();
        //回收子进程
        _cm.WaitProcess();
        //采取了第二种解决方案，因此每个写端只有父进程持有
    }

    ~ProcessPool(){}
private:
    ChannelManager _cm;
    int _process_num;
    TaskManager _tm;
};
